﻿(function () {
    let contentScript = null;

    class WebAutoError extends Error {
        constructor(message) {
            super(message || "")
        }
    }

    class ExtentionApi {
        static GetAllFrames(tabId) {
            return new Promise((resolve, reject)=>{
                chrome.webNavigation.getAllFrames({ tabId: tabId }, (details) => {
                    resolve(details);
                })
            })
        }
    }

    class Handler {
        async invoke(message) {
            if (message.param.tabId) { 
                await new Chrome().ActiveTab(message.param);
            }
            return await this[message.method.split('.')[1]](message.param);
        }
    }

    class Chrome extends Handler {
        InitConScript(param) {
            contentScript = param.code;
        }
        async WindowUpdate(windowId, updateInfo) {
            return new Promise((resolve, reject) => {
                chrome.windows.update(windowId, updateInfo, (wnd) => {
                    resolve(wnd)
                })
            })
        }
        //判断tab页是否加载完成
        async IsLoadCompleted(param) {
            const tab = await new TabFunction.Get(param.tabId)
            if (chrome.runtime.lastError) {
                return false
            }
            return tab.status === 'complete'
        }
        async OpenWebPage(param){
            let urlpath = param.url;
            if(!urlpath.startsWith("http") && !urlpath.startsWith("HTTP")){
                urlpath = "http://"+urlpath
            }
            let createProperties = {url: urlpath}
            let tab  = await TabFunction.CreateTab(createProperties)
            return tab.id;
        }

        DownloadByUrl(param) { 
            return new Promise((resolve, reject) => {
                chrome.downloads.download(param, (downloadId) => {
                    resolve(downloadId)
                })
            })
        }

        async ActiveTab(param) {
            const tab = await TabFunction.Get(param.tabId)
            await new Chrome().WindowUpdate(tab.windowId, {
                focused: true
            })
            await TabFunction.TabUpdate(tab.id, {
                active: true
            })
        }

        SearchDownloadItem(param) { 
            return new Promise((resolve, reject) => {
                chrome.downloads.search(param, (results) => {
                    resolve(results)
                })
            })
        }

        SearchLatestDownloadItem(param) { 
            return new Promise((resolve, reject) => {
                chrome.downloads.search(param, (results) => {
                    resolve(results[0])
                })
            })
        }

        IsDownloadFinished(param) { 
            return new Promise((resolve, reject) => {
                chrome.downloads.search(param, (results) => {
                    if ("complete" == results[0].state) {
                        resolve(true)
                    } else { 
                        resolve(false)
                    }
                })
            })
        }

        async GetOpenedWebPage(param) { 
            let matchType = param.matchType;
            let matchContent = param.matchContent;
            let tabId = -1;
            if (matchType == 0) {
                //当前激活tab
                let tabId = await TabFunction.GetCurrentTabID();
                return tabId;
            } else if (matchType == 1) {
                //根据标题
                let tabs = await TabFunction.Query({ title: matchContent, status: "complete"});
                if (tabs && tabs.length > 0) {
                    return tabs[0].id
                } else { 
                    throw new WebAutoError("没有找到标题为："+matchContent+"网页");
                }
            } else { 
                //根据网址
                let tabs = await TabFunction.Query({ url: matchContent, status: "complete" });
                if (tabs && tabs.length > 0) {
                    return tabs[0].id
                } else { 
                    throw new WebAutoError("没有找到URL为："+matchContent+"网页");
                }
            }
        }
    }

    class Element extends Handler {

        async CatchElement(param) {
            param.frameLevel = new Array();
            let tabId = await TabFunction.GetCurrentTabID();
            //向目标tab请求执行函数，frameid默认为0为顶级frame，methodname为当前methodname
            return await requestOnFrame(tabId, 0, 'CatchElement', param);
        }

        async ClickEle(param){
            return await requestOnFrame(param.tabId, await TabFunction.GetFrameIdByFrameLevel(param.tabId, param.ele.frameLevel), 'ClickEle', param);
        }

        async InputTextToEle(param) { 
            return await requestOnFrame(param.tabId, await TabFunction.GetFrameIdByFrameLevel(param.tabId, param.ele.frameLevel), 'InputTextToEle', param);
        }
        async SelectDropDownValue(param) { 
            let id = await TabFunction.GetFrameIdByFrameLevel(param.tabId, param.ele.frameLevel);
            return await requestOnFrame(param.tabId, id, 'SelectDropDownValue', param);
        }
        async SelectDropDownByIndex(param) { 
            return await requestOnFrame(param.tabId, await TabFunction.GetFrameIdByFrameLevel(param.tabId, param.ele.frameLevel), 'SelectDropDownByIndex', param);
        }

        async WaitEleShowUp(param) { 
            return await requestOnFrame(param.tabId, await TabFunction.GetFrameIdByFrameLevel(param.tabId, param.ele.frameLevel), 'WaitEleShowUp', param);
        }

        async WaitEleDisapper(param) { 
            return await requestOnFrame(param.tabId, await TabFunction.GetFrameIdByFrameLevel(param.tabId, param.ele.frameLevel), 'WaitEleDisapper', param);
        }

        async GetFocus(param) { 
            return await requestOnFrame(param.tabId, await TabFunction.GetFrameIdByFrameLevel(param.tabId, param.ele.frameLevel), 'GetFocus', param);
        }
    }

    class TabFunction {
        static async GetFrameIdByFrameLevel(tabId, frameLevel){
            if(!frameLevel || frameLevel.length === 0){
                return 0;
            }
            let parentId = 0;
            let allFrames = await ExtentionApi.GetAllFrames(tabId);
            for (let level of frameLevel) {
                let otherFrames = [];
                let levelFrames = allFrames.filter(sub => { 
                    if (!sub.parentFrameId == parentId) { 
                        otherFrames.push(sub);
                    }
                    return sub.parentFrameId == parentId
                });
                allFrames = otherFrames;
                let hasFound = false;
                for (let frame of levelFrames) { 
                    let index = await requestOnFrame(tabId, frame.frameId, 'GetFrameIndex')
                    if (index == level) { 
                        hasFound = true;
                        parentId = frame.frameId;
                        break;
                    }
                }
                if (!hasFound) { 
                    break;
                }
            }
            return parentId;
        }

        static Query(queryInfo) {
            return new Promise((resolve, reject) => {
                chrome.tabs.query(queryInfo, (tabs) => {
                    resolve(tabs)
                })
            })
        }

        static Get(tabId) {
            return new Promise((resolve, reject) => {
                chrome.tabs.get(tabId, (tab) => {
                    resolve(tab)
                })
            })
        }

        static GoForward(tabId) {
            return new Promise((resolve, reject) => {
                chrome.tabs.goForward(tabId, () => {
                    resolve()
                })
            })
        }
        static GoBack(tabId) {
            return new Promise((resolve, reject) => {
                chrome.tabs.goBack(tabId, () => {
                    resolve()
                })
            })
        }
        static Remove(tabIds) {
            return new Promise((resolve, reject) => {
                chrome.tabs.remove(tabIds, () => {
                    resolve()
                })
            })
        }
        static GetZoom(tabId) {
            return new Promise((resolve, reject) => {
                chrome.tabs.getZoom(tabId, (zoomFactor) => {
                    resolve(zoomFactor)
                })
            })
        }

        static TabUpdate(tabId, updateInfo) {
            return new Promise((resolve, reject) => {
                chrome.tabs.update(tabId, updateInfo, (tab) => {
                    resolve(tab)
                })
            })
        }
        static CreateTab(createProperties){
            return new Promise((resolve, reject) => {
                chrome.tabs.create(createProperties, (tab) => {
                    resolve(tab)
                })
            })
        }

        static GetCurrentTabID() {
            return new Promise((resolve, reject) => {
                chrome.tabs.query({ "active": true }, (tabs) => {
                    resolve(tabs[0].id);
                })
            })
        }

        static ExecuteOnTargetFrame(tabId, frameId, code) {
            return new Promise((resolve, reject) => {
                let exDetail = {
                    code: code,
                    frameId: frameId, //The frame where the script or CSS should be injected. Defaults to 0 (the top-level frame).
                    matchAboutBlank: true
                }
    
                if (window.opendDialogTabs && opendDialogTabs.indexOf(tabId) > -1) {
                    reject(new WebAutoError(`页面出现JavaScript弹框，请先关闭弹框再操作`))
                }
    
    
                chrome.tabs.executeScript(tabId, exDetail, (result) => {
                    // result存在以下三种情况:
                    // 1. 正常执行返回数组(因为指定了frameId，所以数组中只有一项)
                    // 1.1 有返回值即是数组中的第一项
                    // 1.2 无返回值数组的第一项为null
                    // 2. Javascript执行失败，返回数组，且第一项为null，同1.2
                    // 3. 发生跨域错误，这时需要对chrome.runtime.lastError添加判断，否则会在控制台打印错误信息
    
                    if (chrome.runtime.lastError) {
                        reject(new WebAutoError(chrome.runtime.lastError.message))
                    } else {
                        if (Array.isArray(result) && result.length == 1) {
                            resolve(result[0])
                        } else {
                            reject(new WebAutoError(`${frameId} execute script fail.`))
                        }
                    }
                })

            })
        }
    }

    class HandlerFactory {
        static getHandler(className) {
            switch (className) {
                case "chrome": return new Chrome();
                case "element": return new Element();
                default: return null;
            }
        }
    }

    //tabid代表需要执行注入js的tab页，frameid代表tab内真正执行的frame。默认是0代表主框架frame。若框架不存在frameid则为-1（一般发生在获取frame的parentframe上）
    async function requestOnFrame(tabId, frameId, originalMethod, params) {
        // response:
        // {
        //     status: success(成功),  needInit(需要代码注入), failure(失败), undefined / null
        //         {
        //             存在下面五种情况:
        //             1.success: 成功 { status: 'success', result: result }
        //             2.needInit: content需要初始化 { status: 'needInit', message: 'content need init' }
        //             3.fail: 请求失败
        //                 {
        //                      failure              : { status: 'failure', error: { code: code(-1), message: message } }
        //                 }
        //             4.undefined/null: content script注入失败(出现在某些about:blank页面) 
        //             5、subframe:继续将请求子frame。 { status: 'subframe', route:{frameid:xxx, param: oriparam} }.frameid为-1时代表main frame
        //         }
        // }
        if (!params) {
            params = {}
        }
        params.frameId = frameId
        var code = `window.invoke && invoke('${originalMethod}',${JSON.stringify(params)})`

        var response = await TabFunction.ExecuteOnTargetFrame(tabId, frameId, code)

        if (!response) { //undefined/null
            return null
        } else if (response.status == 'success') { //成功
            return response.result
        } else if (response.status == 'subframe') {
            let allFrames = await ExtentionApi.GetAllFrames(tabId);
            for (let subf of allFrames) {
                if (subf.parentFrameId === frameId) {
                    try {
                        //get tunnel frame && retry in tunnel frame
                        let frameIndex = await requestOnFrame(tabId, subf.frameId, 'GetFrameIndex')
                        if (frameIndex == response.route.frameIndex) {
                            //frameLevel中有几个元素就代表有有几层frame。并且每层的值为在每一层frames中的第几个frame
                            response.route.param.frameLevel.push(frameIndex);
                            return await requestOnFrame(tabId, subf.frameId, originalMethod, response.route.param)
                        }
                    } catch (e) {
                        if (subf.url === 'about:blank') {
                            continue // 忽略about:blank 异常
                        } else {
                            throw e
                        }
                    }
                }
            }
        } else if (response.status == 'bubbling') { //bubbling
            // let details = await new WebNavigationApi().getAllFrames(tabId)
            // for (const detail of details) {
            //     //detail.frameId : The ID of the current frame, or -1 if this is the main frame.  (用===判断)
            //     if (detail.frameId === frameId) {
            //         return await requestOnFrame(tabId, detail.parentFrameId, originalMethod, response.route.args)
            //     }
            // }
        } else if (response.status == 'needInit') { //content need init
            //init contentScript
            await requestOnFrame(tabId, frameId, 'init', {
                code: contentScript
            })
            //retry and return
            return await requestOnFrame(tabId, frameId, originalMethod, params)
        } else if (response.status == 'failure') {
            throw new WebAutoError(response.message)
        }
        throw new WebAutoError(`fail on RequestOnFrame , ${originalMethod}`)
    }







    //创建dispatcher对象,本js脚本内所有函数都需要通过次函数调用分发
    window.dispatcher = new function () {
        const response = {
            ok: (msg, result) => {
                return {
                    code: 1,
                    msg: msg,
                    result: result
                }
            },
            fail: (msg, result) => {
                return {
                    code: -1,
                    msg: msg,
                    result: result
                }
            }
        }


        this.invoke = (message, callback) => {
            try {
                if (message.method.split('.').length != 2) {
                    callback(response.fail("未知的消息格式", ""))
                    return
                }
                const handler = HandlerFactory.getHandler(message.method.split('.')[0]);
                if (handler == null) {
                    callback(response.fail('没有找到Handler，请检查函数名称', ""))
                    return
                }

                handler.invoke(message)
                .then((value) => {
                    callback(response.ok("OK", value))
                }).catch((reason) => {
                    callback(response.fail("error occur when execute inject code.error info is:" + reason.stack, ""))
                });
            } catch (ex) {
                if (ex instanceof WebAutoError) {
                    callback(response.fail("error occur when execute inject code.error info is:" + ex.stack, ""))
                } else {
                    callback(response.fail('未知错误:' + ex.stack, ""))
                }
            }
        }
    }
})()